1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package com.sun.jmx.remote.internal;
27
28 import com.sun.jmx.mbeanserver.Util;
29 import com.sun.jmx.remote.security.NotificationAccessController;
30 import com.sun.jmx.remote.util.ClassLogger;
31 import com.sun.jmx.remote.util.EnvHelp;
32 import java.io.IOException;
33 import java.security.AccessControlContext;
34 import java.security.AccessController;
35 import java.security.PrivilegedAction;
36 import java.security.PrivilegedActionException;
37 import java.security.PrivilegedExceptionAction;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44 import javax.management.InstanceNotFoundException;
45 import javax.management.ListenerNotFoundException;
46 import javax.management.MBeanPermission;
47 import javax.management.MBeanServer;
48 import javax.management.MBeanServerDelegate;
49 import javax.management.MBeanServerNotification;
50 import javax.management.Notification;
51 import javax.management.NotificationBroadcaster;
52 import javax.management.NotificationFilter;
53 import javax.management.ObjectInstance;
54 import javax.management.ObjectName;
55 import javax.management.remote.NotificationResult;
56 import javax.management.remote.TargetedNotification;
57 import javax.management.MalformedObjectNameException;
58 import javax.security.auth.Subject;
59
60 public class ServerNotifForwarder {
61
62
63 public ServerNotifForwarder(MBeanServer mbeanServer,
64 Map<String, ?> env,
65 NotificationBuffer notifBuffer,
66 String connectionId) {
67 this.mbeanServer = mbeanServer;
68 this.notifBuffer = notifBuffer;
69 this.connectionId = connectionId;
70 connectionTimeout = EnvHelp.getServerConnectionTimeout(env);
71 checkNotificationEmission = EnvHelp.computeBooleanFromString(
72 env,
73 "jmx.remote.x.check.notification.emission",false);
74 notificationAccessController =
75 EnvHelp.getNotificationAccessController(env);
76 }
77
78 public Integer addNotificationListener(final ObjectName name,
79 final NotificationFilter filter)
80 throws InstanceNotFoundException, IOException {
81
82 if (logger.traceOn()) {
83 logger.trace("addNotificationListener",
84 "Add a listener at " + name);
85 }
86
87 checkState();
88
89
90
91 checkMBeanPermission(name, "addNotificationListener");
92 if (notificationAccessController != null) {
93 notificationAccessController.addNotificationListener(
94 connectionId, name, getSubject());
95 }
96 try {
97 boolean instanceOf =
98 AccessController.doPrivileged(
99 new PrivilegedExceptionAction<Boolean>() {
100 public Boolean run() throws InstanceNotFoundException {
101 return mbeanServer.isInstanceOf(name, broadcasterClass);
102 }
103 });
104 if (!instanceOf) {
105 throw new IllegalArgumentException("The specified MBean [" +
106 name + "] is not a " +
107 "NotificationBroadcaster " +
108 "object.");
109 }
110 } catch (PrivilegedActionException e) {
111 throw (InstanceNotFoundException) extractException(e);
112 }
113
114 final Integer id = getListenerID();
115
116
117 ObjectName nn = name;
118 if (name.getDomain() == null || name.getDomain().equals("")) {
119 try {
120 nn = ObjectName.getInstance(mbeanServer.getDefaultDomain(),
121 name.getKeyPropertyList());
122 } catch (MalformedObjectNameException mfoe) {
123
124 IOException ioe = new IOException(mfoe.getMessage());
125 ioe.initCause(mfoe);
126 throw ioe;
127 }
128 }
129
130 synchronized (listenerMap) {
131 IdAndFilter idaf = new IdAndFilter(id, filter);
132 Set<IdAndFilter> set = listenerMap.get(nn);
133
134
135 if (set == null)
136 set = Collections.singleton(idaf);
137 else {
138 if (set.size() == 1)
139 set = new HashSet<IdAndFilter>(set);
140 set.add(idaf);
141 }
142 listenerMap.put(nn, set);
143 }
144
145 return id;
146 }
147
148 public void removeNotificationListener(ObjectName name,
149 Integer[] listenerIDs)
150 throws Exception {
151
152 if (logger.traceOn()) {
153 logger.trace("removeNotificationListener",
154 "Remove some listeners from " + name);
155 }
156
157 checkState();
158
159
160
161 checkMBeanPermission(name, "removeNotificationListener");
162 if (notificationAccessController != null) {
163 notificationAccessController.removeNotificationListener(
164 connectionId, name, getSubject());
165 }
166
167 Exception re = null;
168 for (int i = 0 ; i < listenerIDs.length ; i++) {
169 try {
170 removeNotificationListener(name, listenerIDs[i]);
171 } catch (Exception e) {
172
173
174 if (re != null) {
175 re = e;
176 }
177 }
178 }
179 if (re != null) {
180 throw re;
181 }
182 }
183
184 public void removeNotificationListener(ObjectName name, Integer listenerID)
185 throws
186 InstanceNotFoundException,
187 ListenerNotFoundException,
188 IOException {
189
190 if (logger.traceOn()) {
191 logger.trace("removeNotificationListener",
192 "Remove the listener " + listenerID + " from " + name);
193 }
194
195 checkState();
196
197 if (name != null && !name.isPattern()) {
198 if (!mbeanServer.isRegistered(name)) {
199 throw new InstanceNotFoundException("The MBean " + name +
200 " is not registered.");
201 }
202 }
203
204 synchronized (listenerMap) {
205
206
207 Set<IdAndFilter> set = listenerMap.get(name);
208 IdAndFilter idaf = new IdAndFilter(listenerID, null);
209 if (set == null || !set.contains(idaf))
210 throw new ListenerNotFoundException("Listener not found");
211 if (set.size() == 1)
212 listenerMap.remove(name);
213 else
214 set.remove(idaf);
215 }
216 }
217
218
219
220
221
222
223
224
225
226
227
228
229
230 private final NotificationBufferFilter bufferFilter =
231 new NotificationBufferFilter() {
232 public void apply(List<TargetedNotification> targetedNotifs,
233 ObjectName source, Notification notif) {
234
235
236 final IdAndFilter[] candidates;
237 synchronized (listenerMap) {
238 final Set<IdAndFilter> set = listenerMap.get(source);
239 if (set == null) {
240 logger.debug("bufferFilter", "no listeners for this name");
241 return;
242 }
243 candidates = new IdAndFilter[set.size()];
244 set.toArray(candidates);
245 }
246
247
248 for (IdAndFilter idaf : candidates) {
249 final NotificationFilter nf = idaf.getFilter();
250 if (nf == null || nf.isNotificationEnabled(notif)) {
251 logger.debug("bufferFilter", "filter matches");
252 final TargetedNotification tn =
253 new TargetedNotification(notif, idaf.getId());
254 if (allowNotificationEmission(source, tn))
255 targetedNotifs.add(tn);
256 }
257 }
258 }
259 };
260
261 public NotificationResult fetchNotifs(long startSequenceNumber,
262 long timeout,
263 int maxNotifications) {
264 if (logger.traceOn()) {
265 logger.trace("fetchNotifs", "Fetching notifications, the " +
266 "startSequenceNumber is " + startSequenceNumber +
267 ", the timeout is " + timeout +
268 ", the maxNotifications is " + maxNotifications);
269 }
270
271 NotificationResult nr;
272 final long t = Math.min(connectionTimeout, timeout);
273 try {
274 nr = notifBuffer.fetchNotifications(bufferFilter,
275 startSequenceNumber,
276 t, maxNotifications);
277 snoopOnUnregister(nr);
278 } catch (InterruptedException ire) {
279 nr = new NotificationResult(0L, 0L, new TargetedNotification[0]);
280 }
281
282 if (logger.traceOn()) {
283 logger.trace("fetchNotifs", "Forwarding the notifs: "+nr);
284 }
285
286 return nr;
287 }
288
289
290
291
292
293 private void snoopOnUnregister(NotificationResult nr) {
294 Set<IdAndFilter> delegateSet = listenerMap.get(MBeanServerDelegate.DELEGATE_NAME);
295 if (delegateSet == null || delegateSet.isEmpty()) {
296 return;
297 }
298 for (TargetedNotification tn : nr.getTargetedNotifications()) {
299 Integer id = tn.getListenerID();
300 for (IdAndFilter idaf : delegateSet) {
301 if (idaf.id == id) {
302
303 Notification n = tn.getNotification();
304 if (n instanceof MBeanServerNotification &&
305 n.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
306 MBeanServerNotification mbsn = (MBeanServerNotification) n;
307 ObjectName gone = mbsn.getMBeanName();
308 synchronized (listenerMap) {
309 listenerMap.remove(gone);
310 }
311 }
312 }
313 }
314 }
315 }
316
317 public void terminate() {
318 if (logger.traceOn()) {
319 logger.trace("terminate", "Be called.");
320 }
321
322 synchronized(terminationLock) {
323 if (terminated) {
324 return;
325 }
326
327 terminated = true;
328
329 synchronized(listenerMap) {
330 listenerMap.clear();
331 }
332 }
333
334 if (logger.traceOn()) {
335 logger.trace("terminate", "Terminated.");
336 }
337 }
338
339
340
341
342
343 private Subject getSubject() {
344 return Subject.getSubject(AccessController.getContext());
345 }
346
347 private void checkState() throws IOException {
348 synchronized(terminationLock) {
349 if (terminated) {
350 throw new IOException("The connection has been terminated.");
351 }
352 }
353 }
354
355 private Integer getListenerID() {
356 synchronized(listenerCounterLock) {
357 return listenerCounter++;
358 }
359 }
360
361
362
363
364
365 public void checkMBeanPermission(
366 final ObjectName name, final String actions)
367 throws InstanceNotFoundException, SecurityException {
368 SecurityManager sm = System.getSecurityManager();
369 if (sm != null) {
370 AccessControlContext acc = AccessController.getContext();
371 ObjectInstance oi;
372 try {
373 oi = AccessController.doPrivileged(
374 new PrivilegedExceptionAction<ObjectInstance>() {
375 public ObjectInstance run()
376 throws InstanceNotFoundException {
377 return mbeanServer.getObjectInstance(name);
378 }
379 });
380 } catch (PrivilegedActionException e) {
381 throw (InstanceNotFoundException) extractException(e);
382 }
383 String classname = oi.getClassName();
384 MBeanPermission perm = new MBeanPermission(
385 classname,
386 null,
387 name,
388 actions);
389 sm.checkPermission(perm, acc);
390 }
391 }
392
393
394
395
396 private boolean allowNotificationEmission(ObjectName name,
397 TargetedNotification tn) {
398 try {
399 if (checkNotificationEmission) {
400 checkMBeanPermission(name, "addNotificationListener");
401 }
402 if (notificationAccessController != null) {
403 notificationAccessController.fetchNotification(
404 connectionId, name, tn.getNotification(), getSubject());
405 }
406 return true;
407 } catch (SecurityException e) {
408 if (logger.debugOn()) {
409 logger.debug("fetchNotifs", "Notification " +
410 tn.getNotification() + " not forwarded: the " +
411 "caller didn't have the required access rights");
412 }
413 return false;
414 } catch (Exception e) {
415 if (logger.debugOn()) {
416 logger.debug("fetchNotifs", "Notification " +
417 tn.getNotification() + " not forwarded: " +
418 "got an unexpected exception: " + e);
419 }
420 return false;
421 }
422 }
423
424
425
426
427
428 private static Exception extractException(Exception e) {
429 while (e instanceof PrivilegedActionException) {
430 e = ((PrivilegedActionException)e).getException();
431 }
432 return e;
433 }
434
435 private static class IdAndFilter {
436 private Integer id;
437 private NotificationFilter filter;
438
439 IdAndFilter(Integer id, NotificationFilter filter) {
440 this.id = id;
441 this.filter = filter;
442 }
443
444 Integer getId() {
445 return this.id;
446 }
447
448 NotificationFilter getFilter() {
449 return this.filter;
450 }
451
452 @Override
453 public int hashCode() {
454 return id.hashCode();
455 }
456
457 @Override
458 public boolean equals(Object o) {
459 return ((o instanceof IdAndFilter) &&
460 ((IdAndFilter) o).getId().equals(getId()));
461 }
462 }
463
464
465
466
467
468
469 private MBeanServer mbeanServer;
470
471 private final String connectionId;
472
473 private final long connectionTimeout;
474
475 private static int listenerCounter = 0;
476 private final static int[] listenerCounterLock = new int[0];
477
478 private NotificationBuffer notifBuffer;
479 private final Map<ObjectName, Set<IdAndFilter>> listenerMap =
480 new HashMap<ObjectName, Set<IdAndFilter>>();
481
482 private boolean terminated = false;
483 private final int[] terminationLock = new int[0];
484
485 static final String broadcasterClass =
486 NotificationBroadcaster.class.getName();
487
488 private final boolean checkNotificationEmission;
489
490 private final NotificationAccessController notificationAccessController;
491
492 private static final ClassLogger logger =
493 new ClassLogger("javax.management.remote.misc", "ServerNotifForwarder");
494 }